/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package bgu.sonar.util.async;

import bgu.sonar.util.Global;
import bgu.sonar.util.sync.RoadBlock;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 *
 * @author bennyl
 */
public abstract class AsyncTask<T> implements Runnable {

    private volatile Thread t;
    protected FutureResult<T> result;
    private volatile boolean canceled = false;
    private RoadBlock taskDoneBlock = new RoadBlock();
    private volatile boolean redirectedResult = false;
    private AtomicBoolean occupied = new AtomicBoolean(false);

    public AsyncTask() {
        this.t = null;
        this.result = new FutureResult<>(this);
    }

    public FutureResult<T> getResult() {
        return result;
    }

    @Override
    public final void run() {
        if (!occupied.compareAndSet(false, true)) {
            return;
        }

        try {
            t = Thread.currentThread();
            if (canceled) {
                result.failed(new InterruptedException());
            } else {
                try {
                    final T call = call();
                    if (!redirectedResult) {
                        result.success(call);
                    }
                } catch (Exception ex) {
                    if (canceled) {
                        result.failed(new InterruptedException());
                    } else {

                        result.failed(ex);
                    }

                }
            }
        } catch (Exception ex) {
            result.failed(ex);
        } finally {
            t = null;
            taskDoneBlock.remove();
        }
    }

    public void cancel() {
        canceled = true;
        if (t != null) {
            t.interrupt();
        } else {
            result.failed(new InterruptedException());
        }
    }

    public T awaitTermination() throws InterruptedException {
        awaitTerminationNoResult();
        return result.getResult();
    }

    public void awaitTerminationNoResult() throws InterruptedException {
        run(); // attempt to help the execution... 
        taskDoneBlock.pass();
    }

    public boolean isAlive() {
        return t != null;
    }

    public FutureResult<T> executeInGlobalPool() {
        Global.I.getPool().execute(this);
        return getResult();
    }

    public FutureResult<T> executeInSameThread() {
        run();
        return getResult();
    }

    public FutureResult<T> executeInPool(ExecutorService pool) {
        pool.execute(this);
        return getResult();
    }

    public <N> N redirect(final FutureResult<T> alternateResult) {
        redirectedResult = true;
        alternateResult.addCallback(new Callback<T>() {
            @Override
            public void onFailed(Exception ex, boolean interrupted) {
                result.failed(ex);
            }

            @Override
            public void onSuccess(T res) {
                result.success(res);
            }
        });

        result.addCancelHandler(new CancelHandler() {
            @Override
            public void handleCancel(boolean block) {
                if (block) {
                    alternateResult.cancelAndWaitTermination();
                } else {
                    alternateResult.cancel();
                }
            }
        });

        return null;
    }

    public abstract T call() throws Exception;

    public boolean isCanceled() {
        return canceled;
    }

    public static class AlreadyExecutedException extends RuntimeException {

        public AlreadyExecutedException() {
        }

        public AlreadyExecutedException(String message) {
            super(message);
        }

        public AlreadyExecutedException(String message, Throwable cause) {
            super(message, cause);
        }

        public AlreadyExecutedException(Throwable cause) {
            super(cause);
        }
    }
}
